//
//  ZMRefactorAndDestructExtension.swift
//  collectionview的使用
//
//  Created by pg on 2017/7/20.
//  Copyright © 2017年 pg. All rights reserved.
//

import Foundation
import UIKit

enum ZMRefactorDirection{
    case Horizontal
    case Vertical
    case Diagonal
    case Custom
}

enum ZMCellAction{
    case Insert
    case Move
}

extension CGRect{
    var centerPoint: CGPoint{
        return CGPoint(x: origin.x + size.width / 2, y: origin.y + size.height / 2)
    }
}

extension UIView{
    
    func refactor(){
        refactorWithPiecesRegion(jumpRect: nil, assembleRect: nil, shiningColor: nil)
        
    }
    
    
    func customRefactor(){
        
    }

    /**
     Designated refactor method.
     - parameter jumpRect:        the area where all pieces appear, if nil, is 2X frame of the view.
     - parameter shiningColor:    if you specify this parameter, like electric welding.
     - parameter cellAction:      cell action type. Here is only Insert and Move.
     - parameter direction:       the direction of animation
     - parameter animationTime:   the total time of animation
     - parameter ratio:           the ratio which piece to view, here the ratio is used on width and height both.
     - parameter enableBigRegion: you will get 2X or 4X size of general piece if you enable it. I love this, and it can reduce the time of animation. I recommend just enable it if the view is big enough.
     */

    func moveToframe(finallyFrame:CGRect,animationComplation:@escaping(()->())){
        
        guard let _ = self.superview else{
            return
        }
        
        let fromViewSnapshot = self.snapshotView(afterScreenUpdates: true)
        
        self.alpha = 0.0
        
        let ratio: CGFloat = 0.1
        let direction: ZMRefactorDirection = .Vertical
        let animationTime: TimeInterval = 0.2
        
        
      //  let origin = finallyFrame.origin
        let size =  finallyFrame.size
        let pieceWidth: CGFloat = size.width * ratio
        let pieceHeight: CGFloat = size.height * ratio
        
        let (column, row) = columnAndRow(size: size, ratio: ratio)
        let delayDelta: Double = animationTime / Double(column * row)
        let piecesRect = filterRect(jumpRect: self.frame)
        
        var snapshots: [UIView] = []
        
        var ignoreIndexSet:Set<Int> = []
        var index = 0
        var delay: TimeInterval = 0
        var cleanTime: TimeInterval = 0
        
        for y in stride(from: CGFloat(0), to: size.height, by: pieceHeight) {
            
            for x in stride(from: CGFloat(0),to: size.width, by: pieceWidth) {
                
                index += 1
                if ignoreIndexSet.contains(index){
                    continue
                }
                
                let indexOffset = ignoreIndexSet.reduce(0, {
                    delta, element in
                    let gap = element < index ? delta + 1 : delta
                    return gap
                })
                
                
                
                let (snapshotRegion, addedSet) = snapshotInfo(enableBigRegion: false, index: index, xy: (x, y), widthXheight: (pieceWidth, pieceHeight), columnXrow: (column, row))
                
                let initialFrame = randomRectFrom(sourceRect: piecesRect, regionSize: snapshotRegion.size)
                let finalFrame = finallyFrame
                
                if addedSet.count > 0{
                    ignoreIndexSet.formUnion(addedSet)
                }
                
                let snapshot = fromViewSnapshot?.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: false, withCapInsets: UIEdgeInsets.zero)
                self.superview!.addSubview(snapshot!)
                snapshots.append(snapshot!)
                snapshot?.frame = initialFrame
                snapshot?.alpha = 0
                
                let image = (self as! UIImageView).image
                let view : UIImageView = UIImageView(image: image)
                view.contentMode = .scaleAspectFit
                
                view.backgroundColor = UIColor.orange
                snapshot?.addSubview(view)
                view.snp.makeConstraints({ (make) in
                    make.edges.equalToSuperview()
                })
                
                
                switch direction{
                case .Horizontal:
                    let x = index % column == 0 ? column : index % column
                    let y = (index - x + column) / column
                    delay = delayDelta * Double(x * row + y - indexOffset)
                case .Vertical:
                    delay = delayDelta * Double(index - indexOffset)
                case .Diagonal:
                    delay = delayDelta * Double(diagonalIndexFor(index: index, columnXrow: (column, row)))
                case .Custom: break
                }
                
                let duration: TimeInterval = 0.5 + 0.1 * Double(UInt32(arc4random()) % UInt32(3))
                cleanTime = (delay + duration + 0.5 > cleanTime) ? delay + duration + 0.5 : cleanTime
                
            
                UIView.animate(withDuration: 0.1, delay: delay, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
                    snapshot?.alpha = 1
                }, completion: { _ in
                    
                   UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
                    snapshot?.frame = finalFrame
                   }, completion: { (isfinished) in
                    
                      animationComplation()
                    
                      snapshot?.alpha = 0
                   })
                })
                
            
            }
        }
        //can't rely on dispatch_after, which can't guarantee the exact execution time
        self.perform(#selector(cleanUp(snapshots:)), with: snapshots, afterDelay: cleanTime)
        

    }
    
    
    
    func refactorWithPiecesRegion(jumpRect:CGRect?,assembleRect:CGRect?,shiningColor:UIColor?,cellAction:ZMCellAction = .Insert,direction: ZMRefactorDirection = .Horizontal,refactorTime animationTime: TimeInterval = 0.2,ratio: CGFloat = 0.1,enableBigRegion: Bool = false) {
        
        
        guard let _ = self.superview else{
            return
        }
        
        if direction == .Custom{
            customRefactor()
            return
        }

        
        
        let updateScreen = (cellAction == .Insert) ? true : false
        
        let fromViewSnapshot = self.snapshotView(afterScreenUpdates: updateScreen)
        self.alpha = 0.0
        
        let origin = (assembleRect != nil) ? assembleRect!.origin : self.frame.origin
        let size = (assembleRect != nil) ? assembleRect!.size : self.frame.size
        let pieceWidth: CGFloat = size.width * ratio
        let pieceHeight: CGFloat = size.height * ratio
        
        let (column, row) = columnAndRow(size: size, ratio: ratio)
        let delayDelta: Double = (direction == .Diagonal) ? animationTime / Double(column + row - 1) : animationTime / Double(column * row)
        let piecesRect = filterRect(jumpRect: jumpRect)

        var snapshots: [UIView] = []
        
        var ignoreIndexSet:Set<Int> = []
        var index = 0
        var delay: TimeInterval = 0
        var cleanTime: TimeInterval = 0

        for y in stride(from: CGFloat(0), to: size.height, by: pieceHeight) {
            
            for x in stride(from: CGFloat(0),to: size.width, by: pieceWidth) {
                
                index += 1
                if ignoreIndexSet.contains(index){
                    continue
                }
                
                let indexOffset = ignoreIndexSet.reduce(0, {
                    delta, element in
                    let gap = element < index ? delta + 1 : delta
                    return gap
                })
            //    let indexOffset = ignoreIndexSet.
                
                
                let (snapshotRegion, addedSet) = snapshotInfo(enableBigRegion: enableBigRegion, index: index, xy: (x, y), widthXheight: (pieceWidth, pieceHeight), columnXrow: (column, row))
                
                let initialFrame = randomRectFrom(sourceRect: piecesRect, regionSize: snapshotRegion.size)
                let finalFrame = CGRect(origin: CGPoint(x: (x + origin.x), y: (y + origin.y)), size: snapshotRegion.size)
                if addedSet.count > 0{
                    ignoreIndexSet.formUnion(addedSet)
                }
                
                let snapshot = fromViewSnapshot?.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: false, withCapInsets: UIEdgeInsets.zero)
                self.superview!.addSubview(snapshot!)
                snapshots.append(snapshot!)
                snapshot?.frame = initialFrame
                snapshot?.alpha = 0.0
                
                switch direction{
                case .Horizontal:
                    let x = index % column == 0 ? column : index % column
                    let y = (index - x + column) / column
                    delay = delayDelta * Double(x * row + y - indexOffset)
                case .Vertical:
                    delay = delayDelta * Double(index - indexOffset)
                case .Diagonal:
                    delay = delayDelta * Double(diagonalIndexFor(index: index, columnXrow: (column, row)))
                case .Custom: break
                }
                
                let duration: TimeInterval = 0.2 + 0.1 * Double(UInt32(arc4random()) % UInt32(3))
                cleanTime = (delay + duration + 0.2 > cleanTime) ? delay + duration + 0.2 : cleanTime
                
                //In UIView block animation, delay doesn't work as expected, I don't know why. So here use Core Animation.
                //But, with Core Animation, after you insert several cells, animation will be interrupted with unknown reason. At the last, there is no refactor animation.
                //So, here use UIView block animation, just you need to accept a half animation.
                //If you put this refactor in main queue, it work expected. But, again, this will effect other cells's layout. It shake cells behind inserted cell.
                //There is no perfect way for now. If you find the way, please pull a request.
                UIView.animate(withDuration: 0.1, delay: delay, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
                    snapshot?.alpha = 1
                }, completion: { _ in
                    
                    UIView.animate(withDuration: duration, delay: 0, options: UIViewAnimationOptions.beginFromCurrentState, animations: {
                        snapshot?.frame = finalFrame
                    }, completion: nil)
                })
                
                //addRefactorAnimationOn(snapshot, delayTime: delay, duration: duration, initialFrame: initialFrame, finalFrame: finalFrame)
                if shiningColor != nil{
                    addShiningAnimationOn(snapshot: snapshot!, delayTime: delay, shiningColor: shiningColor!)
                }
                
            }
        }
        //can't rely on dispatch_after, which can't guarantee the exact execution time
        self.perform(#selector(cleanUp(snapshots:)), with: snapshots, afterDelay: cleanTime)
        
        
    }
    
    private func snapshotInfo(enableBigRegion: Bool, index: Int, xy: (CGFloat, CGFloat), widthXheight: (CGFloat, CGFloat), columnXrow: (Int, Int)) -> (CGRect, Set<Int>){
    
        let (pieceWidth, pieceHeight) = widthXheight
        
        let isBigRegion = enableBigRegion ? Int(UInt32(arc4random()) % UInt32(2)) == 1 : false
        var regionWidth = isBigRegion ? 2.0 * pieceWidth : pieceWidth
        var regionHeith = isBigRegion ? 2.0 * pieceHeight : pieceHeight
        
        let (regionX, regionY) = xy
        
        let size = self.frame.size
        if regionX + regionWidth >= size.width{
            regionWidth = size.width - regionX
        }
        if regionY + regionHeith >= size.height{
            regionHeith = size.height - regionY
        }
        
        let (column, _) = columnXrow
        var ignoreIndexSet: Set<Int> = []
        
        if isBigRegion {
            if regionX + pieceWidth < size.width{
                ignoreIndexSet.insert(index + 1)
            }
            
            if regionY + pieceHeight < size.height{
                ignoreIndexSet.insert(index + column)
                
                if regionX + pieceWidth < size.width{
                    ignoreIndexSet.insert(index + column + 1)
                }
            }
        }
        
        let regionOrigin: CGPoint = CGPoint(x: regionX, y: regionY)
        let regionSize: CGSize = CGSize(width: regionWidth, height: regionHeith)
        let snapshotRegion = CGRect(origin: regionOrigin, size: regionSize)
        
        return (snapshotRegion, ignoreIndexSet)
    }
  
    
    //MARK: Destruct
    func destruct(){
        destructWithTime()
    }
    
    func destructWithTime(animationTime: TimeInterval = 0.6, direction: ZMRefactorDirection = .Vertical, partionRatio ratio: CGFloat = 0.1){
        guard let _ = self.superview else{
            return
        }
        
        if direction == .Custom{
            //DIY
            return
        }
        
        let fromViewSnapshot = self.snapshotView(afterScreenUpdates: true)
        self.alpha = 0
        
        let origin = self.frame.origin
        let size = self.frame.size
        let pieceWidth = size.width * ratio
        let pieceHeight = size.height * ratio
        let (column, row) = columnAndRow(size: size, ratio: ratio)
        //Here 0.3 is the animation time of single piece
        let totalTime: TimeInterval = animationTime - 0.3
        let delayDelta: Double = (direction == .Diagonal) ? totalTime / Double(column + row - 1) : totalTime / Double(column * row)
        
        var snapshots: [UIView] = []
        var delay:TimeInterval = 0
        var windUpTime: TimeInterval = 0
        var index = 0
        
        for y in stride(from: CGFloat(0), to: size.height, by: pieceHeight) {
            for x in stride(from: CGFloat(0),to: size.width, by: pieceWidth) {
                index += 1
                
                var regionWidth = pieceWidth
                var regionHeight = pieceHeight
                if x + regionWidth > size.width{
                    regionWidth = size.width - x
                }
                if y + regionHeight > size.height{
                    regionHeight = size.height - y
                }
                
                let snapshotRegion = CGRect(x: x, y: y, width: regionWidth, height: regionHeight)
                let snapshot = fromViewSnapshot?.resizableSnapshotView(from: snapshotRegion, afterScreenUpdates: false, withCapInsets: UIEdgeInsets.zero)
                let snapshotFrame = CGRect(x: x + origin.x, y: y + origin.y, width: regionWidth, height: regionHeight)
                
                self.superview?.addSubview(snapshot!)
                snapshot?.frame = snapshotFrame
                snapshots.append(snapshot!)
                
                switch direction{
                case .Horizontal:
                    let x = index % column == 0 ? column : index % column
                    let y = (index - x + column) / column
                    delay = delayDelta * Double(x * row + y)
                case .Vertical:
                    delay = delayDelta * Double(index)
                case .Diagonal:
                    delay = delayDelta * Double(diagonalIndexFor(index: index, columnXrow: (column, row)))
                case .Custom: break
                }
                
                addAnnihilationAnimationaOn(snapshot: snapshot!, delayTime: delay)
                windUpTime = (delay + 0.3 > windUpTime) ? delay + 0.3 : windUpTime
            }
        }
        
        self.perform(#selector(windUp(snapshots:)), with: snapshots, afterDelay: windUpTime)
    }
    private func randomRectFrom(sourceRect: CGRect, regionSize: CGSize) -> CGRect {
        
        //let deltaBase = UInt32(min(min(sourceRect.width, sourceRect.height), 300.0))
        //let delta = CGFloat(arc4random() % deltaBase)
        //let factor: CGFloat = Int(UInt32(arc4random()) % UInt32(2)) == 1 ? -1.0 : 1.0
        
        let randomX: CGFloat = sourceRect.origin.x - sourceRect.size.width / 2 + CGFloat(UInt32(arc4random()) % UInt32( 2.0 * sourceRect.size.width )) //+ factor * delta
        let randomY: CGFloat = sourceRect.origin.y - sourceRect.size.height / 2 + CGFloat(UInt32(arc4random()) % UInt32( 2.0 * sourceRect.size.height )) //- factor * delta
        
        let initialFrame = CGRect(x: randomX, y: randomY, width: regionSize.width, height: regionSize.height)
        
        return initialFrame
    }
    
    private func addRefactorAnimationOn(snapshot: UIView, delayTime: TimeInterval, duration: TimeInterval, initialFrame: CGRect, finalFrame: CGRect){
        let opaAni = CABasicAnimation(keyPath: "opacity")
        opaAni.fromValue = 0
        opaAni.toValue = 1
        opaAni.duration = 0.1
        opaAni.fillMode = kCAFillModeForwards
        opaAni.isRemovedOnCompletion = false
        opaAni.beginTime = CACurrentMediaTime() + delayTime
        snapshot.layer.add(opaAni, forKey: nil)
        
        let moveAni = CABasicAnimation(keyPath: "position")
        moveAni.fromValue = NSValue.init(cgPoint: initialFrame.centerPoint)
        moveAni.toValue = NSValue.init(cgPoint: finalFrame.centerPoint)
        moveAni.duration = duration
        moveAni.beginTime = CACurrentMediaTime() + delayTime
        moveAni.fillMode = kCAFillModeForwards
        moveAni.isRemovedOnCompletion = false
        snapshot.layer.add(moveAni, forKey: nil)
        
    }

    private func addShiningAnimationOn(snapshot: UIView, delayTime: TimeInterval, shiningColor: UIColor, shadowRadius: CGFloat = 10.0){
        snapshot.layer.shadowColor = shiningColor.cgColor
        snapshot.layer.shadowRadius = shadowRadius
        snapshot.layer.shadowPath = UIBezierPath(rect: snapshot.bounds).cgPath
        
        let opaKeyAni = CAKeyframeAnimation(keyPath: "shadowOpacity")
        opaKeyAni.values = [0.0, 0.1, 1.0, 0.8, 0.0]
        opaKeyAni.keyTimes = [0.0, 0.5, 0.7, 0.99, 1.0]
        opaKeyAni.duration = 0.3
        opaKeyAni.beginTime = CACurrentMediaTime() + delayTime
        
        snapshot.layer.add(opaKeyAni, forKey: nil)
        
    }
    
    private func addAnnihilationAnimationaOn(snapshot: UIView, delayTime: TimeInterval){
        let delta = CGFloat(UInt32(arc4random()) % UInt32(50))
       // let end = CGPoint(x: snapshot.center.x + delta, y: snapshot.center.y + delta)
        
        
         let endPoint = CGPoint(x: UIScreen.main.bounds.width*0.5+UIScreen.main.bounds.width*0.125 - delta, y: UIScreen.main.bounds.size.height - CGFloat(20) - delta)
        
//        UIView.animate(withDuration: 0.8, delay: delayTime, options: .curveEaseOut, animations: {
//            snapshot.alpha = 0
//            snapshot.center = endPoint
//            
//         // snapshot.frame =  CGRect(x: endPoint.x, y: endPoint.y, width: 20, height: 20)
//            
//        }, completion: nil)
//        
        UIView.animate(withDuration: 0.8, delay: delayTime, options:  .curveEaseOut, animations: {
            snapshot.alpha = 0
            snapshot.center = endPoint

        }) { (_) in
            self.removeFromSuperview()
        }
    }
 
   
    

//MARK:-返回每行有多少块,每列有多少块
   private func columnAndRow(size:CGSize,ratio:CGFloat)->(Int,Int){
    
    var rowCount = 0
    for _ in stride(from: CGFloat(0), to: size.height, by: size.height*ratio) {
        rowCount += 1
    }
    
    var columnCount = 0
    for _ in stride(from: CGFloat(0), to: size.width, by: size.width*ratio) {
        columnCount += 1
        
    }
    
    return (columnCount,rowCount)
}

 private func filterRect(jumpRect : CGRect?) -> CGRect {
    
    var piecesRect = self.frame
    
    let screenRect = UIScreen.main.bounds
    
    if jumpRect != nil {
        
        if screenRect.contains(jumpRect!) {
            piecesRect = jumpRect!
        }else{
            
            if !screenRect.intersection(jumpRect!).isNull{
                
                piecesRect = screenRect.intersection(jumpRect!)
                
            }else{
                let bigRect = self.frame.insetBy(dx: -self.frame.size.width/2, dy: -self.frame.size.height/2)
                
                piecesRect = screenRect.intersection(bigRect)
           }
            
            
        }
    }else{
        let bigrect =  self.frame.insetBy(dx: -self.frame.size.width/2, dy: -self.frame.size.height/2)
        
        piecesRect = screenRect.intersection(bigrect)
    }
    
    
    return piecesRect
}

    
    private func diagonalIndexFor(index: Int, columnXrow:(Int, Int)) -> Int{
        
        let (column, _) = columnXrow
        let x = index % column == 0 ? column : index % column
        let y = (index - x + column) / column
        let DiagonalIndex = x + y - 1
        return DiagonalIndex
    }


    
     @objc private func cleanUp(snapshots: [UIView]){
        self.alpha = 1
        for snapshot in snapshots{
            snapshot.removeFromSuperview()
        }
        
    }
    
    @objc private func windUp(snapshots: [UIView]){
        for snapshot in snapshots{
            snapshot.removeFromSuperview()
        }
    }


}









